package com.mes.cloud.mail;

import org.springframework.http.MediaType;
import org.springframework.mail.javamail.ConfigurableMimeFileTypeMap;
import org.springframework.util.StringUtils;

import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.FileTypeMap;
import java.io.*;
import java.util.HashMap;
import java.util.Map;


public class Mail {
    // 收件人
    private String to;
    //发件人
    private String from;
    // 邮件主题
    private String subject;
    // 邮件内容
    private String content;
    // 邮件中包含附件
    private Map<String, DataSource> attachments = new HashMap<>();
    // 默认附件false
    private boolean hasAttachment = false;
    // 邮件中包含静态资源
    private Map<String, DataSource> inlines = new HashMap<>();
    // 默认静态资源false
    private boolean hasInline = false;

    private Mail(String to, String subject, String content) {
        this.to = to;
        this.subject = subject;
        this.content = content;
    }

    public static Mail of(String to, String subject, String content) {
        return new Mail(to, subject, content);
    }

    public String getTo() {
        return this.to;
    }

    public String getFrom() {
        return from;
    }

    public Mail from(String from) {
        this.from = from;
        return this;
    }

    public String getSubject() {
        return subject;
    }

    public String getContent() {
        return content;
    }

    public Map<String, DataSource> getAttachments() {
        return attachments;
    }

    public Mail addAttachment(String attachmentName, File attachmentFile) {
        FileDataSource dataSource = new FileDataSource(attachmentFile);
        dataSource.setFileTypeMap(StreamSource.getTypeMap());
        this.attachments.put(attachmentName, dataSource);
        this.hasAttachment = true;
        return this;
    }

    public Mail addAttachment(String attachmentName, InputStream attachmentStream) {
        MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
        try {
            mediaType = MediaType.parseMediaType(StreamSource.getTypeMap().getContentType(attachmentName));
        } catch (Throwable e) {
            //ignored
        }
        return addAttachment(attachmentName, attachmentStream, mediaType);
    }

    public Mail addAttachment(String attachmentName, OutputStream attachmentStream) {
        return addAttachment(attachmentName, attachmentStream.toInputStream());
    }

    public Mail addAttachment(String attachmentName, InputStream attachmentStream, MediaType mediaType) {
        this.attachments.put(attachmentName, new StreamSource(attachmentStream, mediaType, attachmentName));
        this.hasAttachment = true;
        return this;
    }

    public Mail addAttachment(String attachmentName, OutputStream attachmentStream, MediaType mediaType) {
        return addAttachment(attachmentName, attachmentStream.toInputStream(), mediaType);
    }

    public boolean hasAttachment() {
        return this.hasAttachment;
    }

    public Map<String, DataSource> getInlines() {
        return inlines;
    }

    public Mail addInline(String inlineId, File inlineFile) {
        FileDataSource dataSource = new FileDataSource(inlineFile);
        dataSource.setFileTypeMap(StreamSource.getTypeMap());
        this.inlines.put(inlineId, dataSource);
        this.hasInline = true;
        return this;
    }

    public Mail addInline(String inlineId, InputStream attachmentStream, MediaType mediaType) {
        this.inlines.put(inlineId, new StreamSource(attachmentStream, mediaType, "inline"));
        this.hasInline = true;
        return this;
    }

    public Mail addInline(String inlineId, OutputStream attachmentStream, MediaType mediaType) {
        return addInline(inlineId, attachmentStream.toInputStream(), mediaType);
    }

    public boolean hasInline() {
        return this.hasInline;
    }

    @Override
    public String toString() {
        StringBuilder mail = new StringBuilder("Mail:subject[").append(subject).append("],").append("to[").append(to).append("],");

        if (!StringUtils.isEmpty(from)) {
            mail.append("from[").append(from).append("],");
        }

        mail.append("content[").append(content).append("]");

        if (hasAttachment) {
            mail.append(",attachments[").append(StringUtils.collectionToCommaDelimitedString(attachments.keySet())).append("]");
        }

        if (hasInline) {
            mail.append(",inlines[").append(StringUtils.collectionToCommaDelimitedString(inlines.keySet())).append("]");
        }

        return mail.toString();
    }


    public static class OutputStream extends ByteArrayOutputStream {
        public OutputStream() {
            super(4096);
        }

        public InputStream toInputStream() {
            return new ByteArrayInputStream(this.buf, 0, this.count);
        }
    }

    public static class StreamSource implements DataSource {
        private final InputStream inputStream;
        private final MediaType contentType;
        private final String name;

        private static FileTypeMap defaultFileTypeMap;

        public static FileTypeMap getTypeMap() {
            if (defaultFileTypeMap == null) {
                ConfigurableMimeFileTypeMap fileTypeMap = new ConfigurableMimeFileTypeMap();
                fileTypeMap.afterPropertiesSet();
                defaultFileTypeMap = fileTypeMap;
            }
            return defaultFileTypeMap;
        }

        public StreamSource(InputStream inputStream, MediaType contentType, final String name) {
            this.inputStream = inputStream;
            this.contentType = contentType;
            this.name = name;
        }

        @Override
        public InputStream getInputStream() throws IOException {
            inputStream.reset();
            return inputStream;
        }

        @Override
        public java.io.OutputStream getOutputStream() {
            throw new UnsupportedOperationException("Read-only javax.activation.DataSource");
        }

        @Override
        public String getContentType() {
            return contentType.toString();
        }

        @Override
        public String getName() {
            return name;
        }
    }

}
